package qe.spider.trade;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.csv.CsvData;
import cn.hutool.core.text.csv.CsvReadConfig;
import cn.hutool.core.text.csv.CsvRow;
import cn.hutool.core.text.csv.CsvUtil;
import cn.hutool.core.util.NumberUtil;
import lombok.extern.slf4j.Slf4j;
import org.ini4j.Ini;
import qe.spider.vo.GoodStock;
import qe.spider.vo.Stock;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.Charset;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static qe.spider.trade.SendBuySignal.BaseTradePath;

@Slf4j
public class TradeAnalyser {
    private List<Stock> stocks;

    //    private List<GoodStock> holdings;
    private static List<String> notTradeDayList;

    public TradeAnalyser(List<Stock> stocks) {
        this.stocks = stocks;
//        if (holdings == null || holdings.isEmpty()) holdings = parseHoldings();
    }

    public List<GoodStock> parseHoldings(String sellType) {
        File csvBuy = FileUtil.touch(BaseTradePath + System.getProperty("wsn_port", "8888") + "\\buy.csv");
        List<String> lines = FileUtil.readLines(csvBuy, "GBK");
        if (!lines.isEmpty() && lines.get(0).startsWith("股票代码,")) lines = lines.subList(1, lines.size());
        List<GoodStock> buy = lines.stream().map(l -> {
            String[] s = l.split(",");
            GoodStock gs = new GoodStock();
            gs.setCode(s[0]);
            gs.setName(s[1]);
            gs.setUpTime(s[2] + " " + s[3]);
            gs.setUpScore(s[4]);
            gs.setUpRate(s[5]);
            return gs;
        }).collect(Collectors.toList());
        List<GoodStock> solds = parseSolds(sellType);
        return buy.stream().filter(b -> !isSold(b, solds) && b.getUpTime().substring(0, 10).compareTo(DateUtil.today()) < 0).collect(Collectors.toList());
    }

    /***/
    private Boolean isSold(GoodStock b, List<GoodStock> solds) {
        for (GoodStock s : solds) {
            if (s.getCode().equals(b.getCode()) && s.getUpTime().equals(b.getUpTime())) return true;
        }
        return false;
    }

    public void ayalyse() {
        if (!isTradeDay(new Date())) return;
        String currentHHMM = DateUtil.now().substring(11, 16);  //如："09:25"
        if (currentHHMM.compareTo("09:26") >= 0 && currentHHMM.compareTo("09:30") <= 0) { //卖出策略1：开盘卖
            List<GoodStock> holdings = parseHoldings("back_trade_open_sell");
            List<String> holdingsCodes = holdings.stream().map(GoodStock::getCode).collect(Collectors.toList());
            List<Stock> realStocks = stocks.stream().filter(s -> holdingsCodes.contains(s.getStock_code()) && s.currentPrice().isPresent() && s.getKaiPan() != null).collect(Collectors.toList());
            List<String> toSellCodes = realStocks.stream().map(Stock::getStock_code).collect(Collectors.toList());
            List<GoodStock> toSell = holdings.stream().filter(s -> toSellCodes.contains(s.getCode())).collect(Collectors.toList());
            if (!realStocks.isEmpty()) {
                log.warn(">>>开盘卖----{}", realStocks.stream().map(s -> s.codeName() + "（现价：" + s.currentPrice().get() + "，开盘价：" + s.getKaiPan() + "）").collect(Collectors.joining(" , ")));
                recordSellSignal(toSell, realStocks, "back_trade_open_sell");
            }
            log.info("开盘卖股价信息----总数：{}，持仓：{}，持仓股价：{}", stocks.size(), holdingsCodes, stocks.stream().filter(s -> holdingsCodes.contains(s.getStock_code())).map(s -> s.codeName() + ",开盘价：" + s.getKaiPan()).collect(Collectors.joining(";  ")));
        }
        if (currentHHMM.compareTo("09:30") >= 0 && currentHHMM.compareTo("15:00") <= 0) { //卖出策略2：实时现价 < 均价时卖
            List<GoodStock> holdings = parseHoldings("real_trade_fall_avg");
            List<String> holdingsCodes = holdings.stream().map(GoodStock::getCode).collect(Collectors.toList());

            //如果没有均价：现价 < （开盘价+最高价）/2 或 现价<开盘价
//            List<Stock> realStocks = stocks.stream().filter(s -> holdingsCodes.contains(s.getStock_code()) && s.currentPrice().isPresent() && s.getAvgPrice() != null && NumberUtil.isLess(s.currentPrice().get(), s.getAvgPrice())).collect(Collectors.toList());//筛查现价<均价的股票
            List<Stock> realStocks = stocks.stream().filter(s -> holdingsCodes.contains(s.getStock_code()))
                    .filter(s -> {
                        if (s.getAvgPrice() != null && s.currentPrice().isPresent()) {//如果有均价：现价 < 均价
                            return NumberUtil.isLess(s.currentPrice().get(), s.getAvgPrice());
                        } else if (s.currentPrice().isPresent() && s.getKaiPan() != null && s.getHighPrice() != null) { //如果没有均价：现价 < （开盘价+最高价）/2
                            BigDecimal half = s.getKaiPan().add(s.getHighPrice()).divide(new BigDecimal(2), 2, RoundingMode.HALF_UP); //（开盘价+最高价）/2
                            return NumberUtil.isLess(s.currentPrice().get(), half) || NumberUtil.isLess(s.currentPrice().get(), s.getKaiPan());//或 现价<开盘价
                        } else return false;
                    })
                    .collect(Collectors.toList());//筛查现价<均价的股票
            List<String> toSellCodes = realStocks.stream().map(Stock::getStock_code).collect(Collectors.toList());
            List<GoodStock> toSell = holdings.stream().filter(s -> toSellCodes.contains(s.getCode())).collect(Collectors.toList());
            if (!realStocks.isEmpty()) {
                log.warn(">>>出现卖点----现价<均价（或(开盘+最高)/2或开盘价）----{}", realStocks.stream().map(s -> s.codeName() + "（现价：" + s.currentPrice().get() + "，均价：" + s.getAvgPrice() + "，开盘价：" + s.getKaiPan() + "，最高价：" + s.getHighPrice() + "）").collect(Collectors.joining(" , ")));
                recordSellSignal(toSell, realStocks, "real_trade_fall_avg");
            }
            log.info("下穿均价卖股价信息----总数：{}，持仓：{}，持仓股价：{}", stocks.size(), holdingsCodes, stocks.stream().filter(s -> holdingsCodes.contains(s.getStock_code())).map(s -> s.codeName() + ",当前价：" + s.currentPrice() + ",均价：" + s.getAvgPrice() + "，开盘价：" + s.getKaiPan() + "，最高价：" + s.getHighPrice() + "，涨停价：" + s.getZhangTingJia()).collect(Collectors.joining(";  ")));
            if (currentHHMM.compareTo("14:57") >= 0 && currentHHMM.compareTo("15:00") < 0) { //如果临近收盘仍未破均价，且当日未涨停，则在收盘时卖
                List<Stock> realStocks_noLimit = stocks.stream().filter(s -> holdingsCodes.contains(s.getStock_code())).filter(s->!s.isUpLimit()).toList();  //当日未涨停持仓
                List<String> toSellCodes_noLimit = realStocks_noLimit.stream().map(Stock::getStock_code).toList();
                List<GoodStock> toSell_noLimit = holdings.stream().filter(s -> toSellCodes_noLimit.contains(s.getCode())).collect(Collectors.toList());
                if (!realStocks_noLimit.isEmpty()) {
                    log.warn(">>>出现卖点----临收盘未涨停----{}", realStocks_noLimit.stream().map(s -> s.codeName() + "（现价：" + s.currentPrice().get() + "，涨停价：" + s.getZhangTingJia() + "，均价：" + s.getAvgPrice() + "）").collect(Collectors.joining(" , ")));
                    recordSellSignal(toSell_noLimit, realStocks_noLimit, "real_trade_fall_avg");
                }
            }
        }
        if (currentHHMM.compareTo("15:01") >= 0) { //卖出策略3：收盘卖
            List<GoodStock> holdings = parseHoldings("back_trade_close_sell");
            List<String> holdingsCodes = holdings.stream().map(GoodStock::getCode).collect(Collectors.toList());
            List<Stock> realStocks = stocks.stream().filter(s -> holdingsCodes.contains(s.getStock_code()) && s.currentPrice().isPresent() && s.getShoupanPrice() != null).collect(Collectors.toList());
            List<String> toSellCodes = realStocks.stream().map(Stock::getStock_code).collect(Collectors.toList());
            List<GoodStock> toSell = holdings.stream().filter(s -> toSellCodes.contains(s.getCode())).collect(Collectors.toList());
            if (!realStocks.isEmpty()) {
                log.warn(">>>收盘卖----{}", realStocks.stream().map(s -> s.codeName() + "（现价：" + s.currentPrice().get() + "，收盘价：" + s.getShoupanPrice() + "）").collect(Collectors.joining(" , ")));
                recordSellSignal(toSell, realStocks, "back_trade_close_sell");
            }
            log.info("收盘卖股价信息----总数：{}，持仓：{}，持仓股价：{}", stocks.size(), holdingsCodes, stocks.stream().filter(s -> holdingsCodes.contains(s.getStock_code())).map(s -> s.codeName() + ",收盘价：" + s.getShoupanPrice()).collect(Collectors.joining(";  ")));
        }
    }

    public List<GoodStock> parseSolds(String sellFileName) {
        File csvSell = FileUtil.touch(BaseTradePath + System.getProperty("wsn_port", "8888") + "\\" + sellFileName + ".csv");
        List<String> lines = FileUtil.readLines(csvSell, "GBK");
        if (!lines.isEmpty() && lines.get(0).startsWith("股票代码,")) lines = lines.subList(1, lines.size());
        return lines.stream().map(l -> {
            String[] s = l.split(",");
            GoodStock gs = new GoodStock();
            gs.setCode(s[0]);
            gs.setName(s[1]);
            gs.setUpTime(s[2] + " " + s[3]);
            gs.setUpScore(s[4]);
            gs.setUpRate(s[5]);
            return gs;
        }).collect(Collectors.toList());
    }

    /**
     * 取指定日期的前一个交易日
     */
    public Date preTradeDate(Date date) {
        List<Date> preDates = IntStream.range(-10, 0).mapToObj(i -> DateUtil.offsetDay(date, i).toJdkDate()).collect(Collectors.toList());
        return preDates.stream().filter(this::isTradeDay).sorted(Comparator.comparing(Date::getTime).reversed()).findFirst().get();
    }

    /**
     * 判断是否交易日
     */
    public Boolean isTradeDay(Date date) {
        if (DateUtil.isWeekend(date)) return false;
        try {
            if (notTradeDayList == null || notTradeDayList.isEmpty()) {
                Ini ini = new Ini(FileUtil.touch(BaseTradePath + "trade.ini"));
                Map<String, String> map = ini.get("NotTradeDay");
                notTradeDayList = null == map ? new ArrayList<>() : map.keySet().stream().map(s -> DateUtil.parse(s, "yyyyMMdd").toDateStr()).collect(Collectors.toList());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return !notTradeDayList.contains(DateUtil.formatDate(date));
    }

    public void recordSellSignal(List<GoodStock> lastBuys, List<Stock> toSell, String sellType) {
        lastBuys.forEach(gs -> {
            Optional<Stock> so = toSell.stream().filter(ss -> gs.getCode().equals(ss.getStock_code())).findFirst();
            if (so.isPresent()) {
                Stock s = so.get();
                log.info("记录卖出信号----{}----{}({})，上榜价：{},当前：{}", sellType, gs.getCode(), gs.getName(), gs.getUpScore(), s.currentPrice());
                try {
                    SendSellSignal.sell(sellType, gs, s);
                } catch (IOException e) {
                    String day = gs.getUpTime().substring(0, 10).replaceAll("-", "");
                    log.error("卖出{}出错：{}", gs.getCode(), day);
                }
                genSellRecord(gs, s, sellType);
            }
        });
    }

    public void genSellRecord(GoodStock gs, Stock stock, String type) {
        if (!isTradeDay(new Date())) return;    //
        File csvSell = FileUtil.touch(BaseTradePath + System.getProperty("wsn_port", "8888") + "\\" + type + ".csv");
        if (FileUtil.isEmpty(csvSell)) {
            FileUtil.writeString("股票代码,股票名称,买入日期,买入时间,买入价格,买入涨幅,卖出日期,卖出时间,现价,开盘价,收盘价,昨收价,均价,卖出策略,卖出价格,收益,\r\n", csvSell, "GBK");
        }

        CsvData data = CsvUtil.getReader(CsvReadConfig.defaultConfig().setContainsHeader(true)).read(csvSell, Charset.forName("GBK"));
        List<CsvRow> stored = data.getRows().stream().filter(r -> r.getByName("股票代码").equals(gs.getCode()) && r.getByName("买入日期").equals(gs.getUpTime().substring(0, 10))).collect(Collectors.toList());

//        List<String> lines = FileUtil.readLines(csvSell, "GBK");
//        List<String> stored = lines.stream().filter(line -> line.contains(gs.getCode()) && line.contains(gs.getUpTime().substring(0, 10))).collect(Collectors.toList());
        if (!stored.isEmpty()) return;  //已存当日该股，就不再保存了
        BigDecimal buyPrice = NumberUtil.toBigDecimal(gs.getUpScore());
        BigDecimal sellPrice = BigDecimal.ZERO;
        if ("back_trade_open_sell".equals(type)) sellPrice = stock.getKaiPan();
        else if ("back_trade_close_sell".equals(type)) sellPrice = stock.getShoupanPrice();
        else if ("real_trade_fall_avg".equals(type)) sellPrice = stock.currentPrice().orElse(BigDecimal.ZERO);
        String recode = gs.getCode().replaceAll(",", "|") + ","
                + gs.getName().replaceAll(",", "|") + ","
                + gs.getUpTime().substring(0, 10) + ","
                + gs.getUpTime().substring(11) + ","
                + gs.getUpScore().replaceAll(",", "|") + ","
                + gs.getUpRate().replaceAll(",", "|") + ","

                + DateUtil.today().substring(0, 10) + ","   //卖出日期
                + DateUtil.now().substring(11) + ","   //卖出时间
                + stock.currentPrice().orElse(BigDecimal.ZERO).toString().replaceAll(",", "|") + ","    //现价
                + (stock.getKaiPan() == null ? "" : stock.getKaiPan().toString().replaceAll(",", "|")) + ","   //开盘
                + (stock.getShoupanPrice() == null ? "" : stock.getShoupanPrice().toString().replaceAll(",", "|")) + "," //收盘
                + (stock.getZuoShou() == null ? "" : stock.getZuoShou().toString().replaceAll(",", "|")) + "," //昨收
                + (stock.getAvgPrice() == null ? "" : stock.getAvgPrice().toString().replaceAll(",", "|")) + ","     //均价;
                + type.replaceAll(",", "|") + ","     //卖出策略;
                + sellPrice.toString().replaceAll(",", "|") + ","    //卖出价格;
                + NumberUtil.formatPercent(sellPrice.subtract(buyPrice).divide(buyPrice, 4, RoundingMode.HALF_UP).doubleValue(), 2) + ",\r\n"     //收益;
                ;
        FileUtil.appendString(recode, BaseTradePath + System.getProperty("wsn_port", "8888") + "\\" + type + ".csv", "GBK");
    }

    public static void main(String[] args) {
        Date date = DateUtil.parse("2024-04-07", "yyyy-MM-dd").toJdkDate();
        Date lastTradeDay = new TradeAnalyser(new ArrayList<>()).preTradeDate(date);
        System.out.println(DateUtil.formatDate(lastTradeDay));
        System.out.println(DateUtil.now().substring(11, 16));
    }
}